iT邦幫忙

2019 iT 邦幫忙鐵人賽

5
Modern Web

Angular 深入淺出三十天系列 第 31

[Angular 深入淺出三十天] Day 30 - Angular 小學堂(四之四)

  • 分享至 

  • xImage
  •  

昨天的登入頁、購物車頁與結帳成功頁大家都有順利套完版嗎?!

如果沒有的話沒關係,看完今天的文章再繼續努力吧!!


結帳頁套版

今天一開始我們先來套結帳頁的版型,打開 assets/css/ 裡的 checkout.css 檔,一樣全部複製下來之後,貼到我們專案裡的 checkout.component.css 裡。

一樣要記得調整檔案路徑噢!!忘記怎麼修改的話可以看昨天的文章複習一下。

接著打開素材 pages/ 裡的 checkout-1.html,一樣只要複製中間的區塊就好:

Imgur

複製貼上到我們專案裡的 checkout.component.html 之後,就可以先來看一下畫面:

Imgur

耶!!套完了!!

還沒啦!!

這邊其實會稍微要大家跟著我處理一下,因為當初其實沒有想說要切路由,直接用狀態切換就好。

不過在寫文章的時候又想說,就多練習一些巢狀路由的設定也不錯,才把這邊切成其他路由。

所以接下來就要開始處理這部分了!!

首先先打開我們專案的 checkout.component.css 檔,然後開啟搜尋/取代的功能 (masOS 請按下 command + option + F , Windows 請按下 ctrl + H ,將整個檔案裡的 article 全部取代為 article /deep/

像這樣:

Imgur

最上面那兩個我移除是因為那兩個不需要改成 article /deep/

接著再到打開 checkout.component.html ,然後剪下這個區塊裡的程式碼:

Imgur

整個貼到 customer-info.component.html 裡,儲存後看一下畫面,應該就會跟剛剛一開始的時候長得一模一樣。

感謝邦友 jackson09 的留言,我才發現這段描述沒寫完全。^^"
重新調整如下:

整個貼到 customer-info.component.html 裡並儲存後,要在 checkout.component.html 剛剛剪下的那個區塊裡加上一個路由插座 <router-outlet></router-outlet> ,像是這個樣子:

<div class="container">

  <!-- 表單區域開始 -->
  <article class="font-light-green bg-dark-green">

    <!-- 在這裡加上路由插座 -->
    <router-outlet></router-outlet>

  </article>
  <!-- 表單區域結束 -->

  <!-- 側邊欄區域開始 -->
  <aside class="desktop">
    
    <!-- ... -->
    
  </aside>
  <!-- 側邊欄區域結束 -->

</div>

儲存後看一下畫面,應該就會跟跟剛剛一開始的時候長得一模一樣囉!

Angular 小知識:

在 Angular 裡,每個 component 的 css 是互不影響的,避免命名衝突以及讓程式碼更容易被維護。
不過如果像遇到這種一定要從父層設定樣式,且要能夠一併調整子層 Component 的樣式時,就需要加上 /deep/ 的特殊字來讓樣式能夠套用在子層上。

畫面沒問題之後,我們接下來要先改先打開 customer-info.component.ts ,然後 import 我們之前定義好的 'appPath' 進來,像這樣:

// Constant
import { appPath } from '../../app-path.const';

並在 CustomerInfoComponent 裡新增一個名為 path 的屬性,再將 appPath 指定給它,像這樣:

export class CustomerInfoComponent implements OnInit {

  /**
   * 給 Template 用的路由定義
   *
   * @memberof CustomerInfoComponent
   */
  path = appPath;

}

以上的動作在接下來的 PaymentInfoComponent 跟 ReceiptInfoComponent 都一樣,就不再贅述囉!

接著把 customer-info.component.html 裡的下一步這個按鈕的連結改為:

<a [routerLink]="['..', path.checkoutFlow.paymentInfo]">下一步</a>

咦?有發現這個連結長得有點奇怪嗎?

其實如果昨天有練習購物車頁的套版的話,應該會遇到路由設定的問題,因為在巢狀路由下的時候,連結是往子層下面找的。

我們用 Augury 幫我們畫的圖來解釋:

Imgur

是不是超清楚的?! Augury 超好用的!!

一開始我們的共用層連結是在 AppComponent 裡,所以在設定連結的時候只要像這樣即可:

<ul>
  <li><a [routerLink]="[path.home]">首頁</a></li>
  <li><a [routerLink]="[path.products]">甜點</a></li>
  <li><a [routerLink]="[path.login]">登入</a></li>
</ul>

因為路由機制會從 Appcomponent 往下找的關係,所以從上圖中的結構就能清楚地看到,路由機制可以找到相對應的路徑,沒有問題。

但如果像是現在我們要設定的連結是在 CustomerInfoComponent 裡的時候,如下圖紅框處:

Imgur

這時候如果我們連結設成這樣:

<a [routerLink]="path.checkoutFlow.paymentInfo">下一步</a>

可想而知,路由機制會往 CustomerInfoComponent 的下一層去找路由。

但有這個路由嗎?沒有。

所以這時候我們可以這樣設:

<a [routerLink]="['..', path.checkoutFlow.paymentInfo]">下一步</a>

'..' 有一種回上一層的感覺,然後再由上一層的位置往下找 path.checkoutFlow.paymentInfo 這個變數儲存的路徑,也就是 PaymentInfoComponent 所對應的路由。

設定好之後,應該就能夠正常導頁了:

Imgur

再來讓我們把 PaymentInfoComponent 跟 ReceiptInfoComponent 迅速地套版套一套,他們各自對應的素材檔案是 pages/ 底下的 checkout-2.htmlcheckout-3-1.htmlcheckout-3-2.html

PaymentInfoComponent 的套版非常簡單,只要將 checkout-2.html 裡的這塊程式碼:

Imgur

複製貼上到 payment-info.component.ts 裡,然後一樣修改一下連結:

<a [routerLink]="['..', path.checkoutFlow.receiptInfo]">下一步</a>

這樣就完工了,超簡單的吧?!

ReceiptInfoComponent 的套版其實也差不多,一樣將 checkout-3-1.html 裡的這塊程式碼:

Imgur

複製貼到上 receipt-info.component.ts 裡,然後修改一下連結:

<p class="full button"><a [routerLink]="['/', path.success]">確認結帳</a></p>

咦?!這次連結怎麼又不一樣了?!

這是因為之前用的 .. ,它最多只能用一次,也就是像上面這樣:

<a [routerLink]="['..', path.checkoutFlow.receiptInfo]">下一步</a>

但如果要用兩次以上像是這樣的話:

<a [routerLink]="['..', '..', path.success]">下一步</a>

路由機制讀取到的路徑就會變成 /checkout/../success ,然後就會被萬用路由轉回 /checkout/customer-info 了。

接下來打開素材 assets/css/ 底下的 checkout-3.css ,複製這邊的樣式設定到 receipt-info.component.css 裡,然後用上次提到的全部取代的功能將 .container article 全部取代為空字串。

像這樣:

Imgur

樣式設定完之後,這頁有個可以 swtich 的按鈕可以切換要顯示電子發票的表單區塊還是郵寄發票的表單區塊:

Imgur

這塊要怎麼處理咧?!

其實很簡單,利用我們學過的事件綁定再加上 *ngIf 來判斷要顯示的區塊就可以了!

首先我想先用 CLI 建個列舉來代表不同寄送方式:

ng generate enum checkout/receipt-info/send-type

然後打開 send-type.enum.ts , 輸入以下程式碼:

/**
 * 寄送發票的方式的列舉
 *
 * @export
 * @enum {number}
 */
export enum SendType {

  // 用 Email 寄送電子發票
  EMAIL,

  // 寄送實體發票到指定地址
  ADDRESS

}

接下來將這個列舉 import 到 receipt-info.component.ts 裡使用並將程式碼調整成這樣:

import { Component, OnInit } from '@angular/core';

// Constant
import { appPath } from 'src/app/app-path.const';

// Enum
import { SendType } from './send-type.enum';

@Component({
  selector: 'app-receipt-info',
  templateUrl: './receipt-info.component.html',
  styleUrls: ['./receipt-info.component.css']
})
export class ReceiptInfoComponent implements OnInit {

  /**
   * 給 Template 用的路由定義
   *
   * @memberof ReceiptInfoComponent
   */
  path = appPath;

  /**
   * 給 Template 用的寄送方式列舉
   *
   * @memberof ReceiptInfoComponent
   */
  sendType = SendType;

  /**
   * 當前的寄送類型,預設使用 Email
   *
   * @memberof ReceiptInfoComponent
   */
  selectedType = SendType.EMAIL;

  constructor() { }

  ngOnInit() {
  }

  /**
   * 切換寄送類型
   *
   * @param {number} type - 欲切換的寄送類型
   * @memberof ReceiptInfoComponent
   */
  switch(type: number): void {
    this.selectedType = type;
  }

  /**
   * 傳入的寄送類型是否為當前所選擇的寄送類型
   *
   * @param {number} type - 欲判斷的寄送類型
   * @returns {boolean}
   * @memberof ReceiptInfoComponent
   */
  didSelected(type: number): boolean {
    return this.selectedType === type;
  }

}

然後我們再把 receipt-info.component.ts 裡的這段 HTML :

<div class="segment font-light-green">
  <ul>
    <li class="selected"><a href="javascript:;">電子發票</a></li>
    <li><a href="checkout-3-2.html">郵寄發票</a></li>
  </ul>
</div>

改成:

<div class="segment font-light-green">
  <ul>

    <li [class.selected]="didSelected(sendType.EMAIL)">
      <a
        href="javascript:;"
        (click)="switch(sendType.EMAIL)"
      >
        電子發票
      </a>
    </li>

    <li [class.selected]="didSelected(sendType.ADDRESS)">
      <a
        href="javascript:;"
        (click)="switch(sendType.ADDRESS)"
      >
        郵寄發票
      </a>
    </li>

  </ul>
</div>

然後再把這段 HTML :

<form>

  <p class="full">
    <label for="email">電子郵件信箱</label>
    <input type="email" id="email" placeholder="example@email.com">
  </p>

  <p class="full">
    <label for="company-code">統一編號(選填)</label>
    <input type="text" id="company-code" placeholder="12345678">
  </p>

</form>

<ng-container></ng-container> 將兩個 <p></p> 像這樣包起來:

<form>

  <!-- 電子發票所需之表單區域開始 -->
  <ng-container>

    <p class="full">
      <label for="email">電子郵件信箱</label>
      <input type="email" id="email" placeholder="example@email.com">
    </p>

    <p class="full">
      <label for="company-code">統一編號(選填)</label>
      <input type="text" id="company-code" placeholder="12345678">
    </p>

  </ng-container>
  <!-- 電子發票所需之表單區域結束 -->
  
</form>

接著再新增一個 <ng-template></ng-template> ,然後把素材 pages/ 裡的 checkout-3-2.html 打開,把以下這段的 HTML 複製下來:

Imgur

貼到剛剛新增的 <ng-template></ng-template> 裡,像這樣:

<form>

  <!-- 電子發票所需之表單區域開始 -->
  <ng-container>
    <!-- ... -->
  </ng-container>
  <!-- 電子發票所需之表單區域結束 -->

  <!-- 郵寄發票所需之表單區域開始 -->
  <ng-template>

      <p class="address full">

        <label for="address">地址</label>

        <select name="city" id="city">
          <option value="高雄市">高雄市</option>  
        </select>

        <select class="float-right" name="county" id="county">
            <option value="新興區">新興區</option>  
        </select>

        <input type="text" id="address" placeholder="幸福路 520 號">

        <span class="same-checkbox">
          <input type="checkbox" name="same-address" id="same-address">
          <label for="same-address">同運送地址</label>
        </span>

      </p>

      <p class="full">
        <label for="company-code">統一編號(選填)</label>
        <input type="text" id="company-code" placeholder="12345678">
      </p>

  </ng-template>
  <!-- 郵寄發票所需之表單區域結束 -->
  
</form>

最後再把 *ngIf 的判斷加入:

<form>

  <!-- 電子發票所需之表單區域開始 -->
  <ng-container *ngIf="didSelected(sendType.EMAIL); else addressArea">
    <!-- ... -->
  </ng-container>
  <!-- 電子發票所需之表單區域結束 -->

  <!-- 郵寄發票所需之表單區域開始 -->
  <ng-template #addressArea>
    <!-- ... -->
  </ng-template>
  <!-- 郵寄發票所需之表單區域結束 -->
  
</form>

完成了!來看看效果吧:

Imgur

好的,如此一來我們就完成了結帳頁的套版了!!

至於甜點那頁的就留給你們練習囉?!

範例程式碼的部份我會上傳到 Github 上讓大家參考,如果有任何的問題也歡迎在文章底下留言或是傳訊給我。

目前的系列賽文章我預計會再發兩篇文章,一篇是關於如果已經跟著這個系列的文章練習過也研讀過一遍的話,接下來的學習方向以及學習資源分享;另外一篇則是完賽心得。

敬請期待!!

錯誤更新記錄

  • 2019/11/18 17:32 - 非常感謝邦友 obelisk0114 的提醒,修正 appPath 的引入路徑有誤之問題。

上一篇
[Angular 深入淺出三十天] Day 29 - Angular 小學堂(四之三)
下一篇
[Angular 深入淺出三十天] Day 31 - 三十天之後
系列文
Angular 深入淺出三十天33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
開心學編程
iT邦新手 5 級 ‧ 2018-11-23 15:38:48

有個問題額,用ng build --prod打包的話,會報錯
Invalid configuration of route '': routes must have either a path or a matcher specified

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2018-11-23 18:35:36 檢舉

非常感謝你發現問題!!

麻煩你稍等我一下,我處理完再回報狀況讓你知道!

Leo iT邦新手 3 級 ‧ 2018-11-23 20:07:01 檢舉

開心學編程,我這邊試過是沒有問題的耶~~~@@

如下圖所示:

Imgur

會不會是你那邊有調整過程式碼?

你再試試看~ 如果有什麼問題再 PO 上來我們一起討論囉!

catstar iT邦新手 5 級 ‧ 2018-12-06 10:55:07 檢舉

同一樓問題,編譯時不會有問題
是必須用瀏覽器去檢視console才會出現
使用ng build 不會出錯
使用ng build --prod console報錯
部屬至IIS,ROUTE中的USEHASE已設為TRUE

Leo iT邦新手 3 級 ‧ 2018-12-13 09:37:52 檢舉

Hi Catstar,

抱歉這麼晚才發現你的留言 >_<"

所以你的問題是已經解決了嗎?還是還存在呢?

如果還存在,方便截圖讓我看一下完整的錯誤訊息嗎?

0
jackson09
iT邦新手 5 級 ‧ 2018-12-13 09:31:58

Leo大大 有問題詢問~

整個貼到 customer-info.component.html 裡,儲存後看一下畫面,應該就會跟剛剛一開始的時候長得一模一樣。<

剪下貼到customer-info.component.html,進行這一步後我個人資料清單不見了 T_T
https://ithelp.ithome.com.tw/upload/images/20181213/20113704fHgBAvf6XN.png
我的checkout.component.html、coustomer-info.component.html、checkout.component.css如下:
https://ithelp.ithome.com.tw/upload/images/20181213/20113704foSHDg6y4l.png

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2018-12-13 10:02:50 檢舉

Hi Jackson,

非常感謝你的留言!該段落是我沒有描述清楚。

我重新調整了一下描述,請你再看一次並練習看看。

如果還有問題請你務必讓我知道,感激不盡!

/images/emoticon/emoticon41.gif

jackson09 iT邦新手 5 級 ‧ 2018-12-13 10:41:09 檢舉

感謝您的更新 已解決上述問題
但下一步我又卡關了 需要求救(sorry)

畫面沒問題之後,我們先把 customer-info.component.html 裡的下一步這個按鈕的連結改為:
<a [routerLink]="['..', path.checkoutFlow.paymentInfo]">下一步

把原本的a href改成a [routerLink]後它無法點擊連結
https://ithelp.ithome.com.tw/upload/images/20181213/20113704MVV7lbwECM.png
https://ithelp.ithome.com.tw/upload/images/20181213/20113704mEKMwe7PPY.png

點擊"下一步"後顯示error
https://ithelp.ithome.com.tw/upload/images/20181213/20113704cF15UjzGI0.png

Leo iT邦新手 3 級 ‧ 2018-12-13 11:32:49 檢舉

Hi Jackson,

抱歉,這邊又是我漏了敘述。

我重新調整過敘述了!麻煩你再看一次並練習看看。

如果還有問題請你務必讓我知道,感激不盡!

/images/emoticon/emoticon41.gif

jackson09 iT邦新手 5 級 ‧ 2018-12-13 12:02:45 檢舉

感謝Leo大大的耐心回應s 我完成了!接下來要自己捉摸甜點頁/images/emoticon/emoticon13.gif

另外發現一些引用路徑s有誤

複製貼上到 payment-info.component.ts 裡,然後一樣修改一下連結:
複製貼到上 receipt-info.component.ts 裡,然後修改一下連結:
然後我們再把 receipt-info.component.ts 裡的這段 HTML :

這三句應該都是引用html檔而非ts檔 再次感謝你的文章/images/emoticon/emoticon41.gif

Leo iT邦新手 3 級 ‧ 2018-12-13 15:33:07 檢舉

Hi Jackson,

很高興這個系列文跟我都有幫上你的忙 ^^

加油!!/images/emoticon/emoticon12.gif

1
ice bear
iT邦新手 4 級 ‧ 2019-09-04 16:24:05

HI 大大你好!

套板完成確認畫面跟route正常之後
也把甜點頁做出來了

但我想說試著做做看如果甜點名稱不要寫死
直接是一個ts檔
像這樣
https://ithelp.ithome.com.tw/upload/images/20190904/20120596gxqGaRsuWG.png

但想請問
我實際把甜點頁做出來之後(旁邊的甜點類別也可以做filter了!)
https://ithelp.ithome.com.tw/upload/images/20190904/20120596OVKecddxBG.png

可是因為我甜點項目很多,所以導致卷軸要卷很下面
可是我看到你最下面有一個這樣的東西,可以切換到下一頁,雖然是寫死的
https://ithelp.ithome.com.tw/upload/images/20190904/201205961jRG696ZAs.png

我想問問這個可以怎麼實作?
我有想到在上面綁click事件,是否還有更簡便的方法呢?

Leo iT邦新手 3 級 ‧ 2019-09-04 16:30:20 檢舉

Hi a405066,

恭喜你完成套版!!

沒錯,你的方向都很正確!

分頁按鈕是我我也會綁 click ,然後用頁碼去算當前要顯示資料中的幾筆到幾筆(例如第一頁顯示第 0 筆到 第 9 筆,共十筆)。

加油加油!!有問題儘管發問! :)

0
ice bear
iT邦新手 4 級 ‧ 2019-09-04 18:11:54

HI 大大你好

謝謝你的回覆!!

我想再問個問題QQ 很抱歉 第一次學前端問題有點多QQ

我目前在實作點甜點按鈕下的加入購物車,然後點購物車可以看到剛剛點過的清單
https://ithelp.ithome.com.tw/upload/images/20190904/20120596woMkQfNEB2.png

但現在有個問題
巧克力熔岩我點了兩次,所以購物車產生了兩筆,可是我點其中一筆的'+'時,會跟著連動,兩筆的數量會變成一樣的,能幫我看看是為什麼嗎QQ

我在計算數量的地方,已經有傳入index了,應該不會這樣才對QQ
下面是相關的程式碼...

在計算數量的地方,我是直接把清單上記錄的數量改掉,改掉後再修改該筆紀錄的總金額,讓前端看到的畫面可以連動
https://ithelp.ithome.com.tw/upload/images/20190904/20120596aRkQBixIAV.png

component的地方則是直接傳入index
https://ithelp.ithome.com.tw/upload/images/20190904/20120596jJq2Uf7i5T.png

為什麼會導致他的連動呢QQ

Leo iT邦新手 3 級 ‧ 2019-09-04 18:21:11 檢舉

Hi a405066,

簡單來說,之所以會連動就是因為他們其實都參考到同一個變數位置的關係。

因為你目前較不熟悉 JavaScript 特性,你可以閱讀一下有關於 JavaScript「淺拷貝」與「深拷貝」的相關資訊。

0
ice bear
iT邦新手 4 級 ‧ 2019-09-12 15:40:51

HI 大大你好

我又來問問題了QQ

想請問 header和footer 只能放在appcomponent才能作用嘛?
因一樣的段落我放在appcomponet能顯示,但我移進別的component就被吃掉了QQ

Leo iT邦新手 3 級 ‧ 2019-09-12 16:31:20 檢舉

Hi a405066,

當然可以移呀,但是你要把相關宣告也都移走才會有作用噢!

0
obelisk0114
iT邦新手 5 級 ‧ 2019-11-18 17:09:07
  1. 下面這段有誤

我們接下來要先改先打開 customer-info.component.ts ,然後 import 我們之前定義好的 'appPath' 進來,像這樣:

// Constant
import { appPath } from './app-path.const';

應該是 
import { appPath } from '../../app-path.const';
<p class="full button"><a [routerLink]="['/', path.success]">確認結帳</a></p>

其中的 '/' 是跳回到 appComponent 嗎 ? 我將 index.htmlbase href 改為 /12345 也可以成功轉到 success 頁面

  1. 上面好像可以用
<p class="full button"><a [routerLink]="['../..', path.success]">確認結帳</a></p>
Leo iT邦新手 3 級 ‧ 2019-11-18 17:35:56 檢舉

Hi obelisk0114,

  1. 感謝告知!已修正!
  2. 使用 / 是讓它從根路徑算起,我在這邊這樣用算是實驗性質,實際上我個人不太會這樣用。
  3. 沒錯,可以這樣用。
0
obelisk0114
iT邦新手 5 級 ‧ 2019-11-26 13:51:42

大大你好,我碰到了一個和樓上有點像的問題

我用 product.service 來儲存購物車裡面的商品,並定義了 IProductinterface 來表示和商品有關的資訊。

我將商品放入購物車 (在 product-list.component 點擊 加入購物車 按鈕) 時,會同時檢查這個商品是否存在 (id 相同表示同一商品),但是我發現同一商品點了數次,商品的數量卻不會被更新
https://ithelp.ithome.com.tw/upload/images/20191126/201226394bpvwSz4nh.png

這是 IProduct

export interface IProduct {
  id: number;
  type: string;
  name: string;
  price: number;
  imageUrl: string;
  quantity: number;
}

這是 product-list.component.html 相關的部分

        <button class="font-dark-green bg-light-green"
                (click)="addCart(product, 1)">
            加入購物車
        </button>

這是 product-list.component.ts 相關的部分

  addCart(element: IProduct, quantity: number): void {
    let product: IProduct = element;
    product.quantity = quantity;

    this.productService.addProduct(product);
  }

這是 product.service 相關的部分

  selectedProducts: IProduct[] = [];
  addProduct(product: IProduct): void {
    let target = this.selectedProducts.findIndex(
      (p: IProduct) => p.id === product.id);
    if (target !== -1) {
      console.log(`target quantity =
        ${this.selectedProducts[target].quantity} ; product = 
        ${product.quantity}`);
      this.selectedProducts[target].quantity += product.quantity;
    }
    else {
      this.selectedProducts.push(product);
    }
    
    this.price = this.price + product.price * product.quantity;
    this.shipping = 300;
  }
Leo iT邦新手 3 級 ‧ 2019-11-26 15:33:29 檢舉

Hi obelisk0114,

照你的程式寫法來看的話,這是實體參照的問題。

你新增到 selectedProductsproduct 必須與你畫面上的 product 的實體參照不一樣,否則當你在 product-list.component.tsaddCart 函式裡讓 product.quantity = quantity; 的時候,就已經把原本在 selectedProducts 裡的同一個 product 的數量改為 1 了。

By the way, 改的時候注意淺拷貝與深拷貝的問題。

寫太快忘記重新建立物件。感謝大大說明

Leo iT邦新手 3 級 ‧ 2019-11-27 09:19:55 檢舉

很高興能夠幫上忙 :)

0
obelisk0114
iT邦新手 5 級 ‧ 2019-11-27 12:45:02

大大你好,重開一篇來問一點其他的問題。

  1. 若這是完整的網站,大大會將哪些部分交給後端 ?
  2. 大大會將 service 集中放到一個 service 資料夾還是分開 ?

對於第一個問題,以下是我粗淺的想法
login 頁會用 http post 將帳號、密碼傳給後端。若正確,後端會將 email、地址等資料回傳前端,使用者可以瀏覽 profile 頁面 (目前還不在大大的架構裡,使用 canActivate 控制)。結帳頁會用使用者的資料來預先填入。

Product section 會向後端請求產品數量 (甜點後面的數字)。Product list 部分就比較不了解

原先是想說後端向前端發送 本日精選,若使用者點選其他的類別,像是 人氣推薦,前端再向後端請求資料。但是這樣做會有 http 過度使用問題。前端 - 後端 - 資料庫 - 後端 - 前端,除了出錯風險提高 (網路問題),所花時間也較長。

若後端直接將所有產品都傳給前端,所有處理都在前端,後端不用做事,前後端 server loading 會很奇怪。若加入產品搜索框會更明顯。

至於第二個問題,我目前有 product.service 來管理購物車,會在 product-list、cart、結帳頁被使用。之後還會加入一個負責拿資料的 service 目前是將它們放在 product-section 資料夾,不過我在想是不是將 service 集中放到一個 service 資料夾較好 ?

Leo iT邦新手 3 級 ‧ 2019-11-27 13:36:37 檢舉

Hi obelisk0114,

  1. 以一個 EC 網站來說,基本應該要有以下功能
    • 會員相關功能(登入、登出、變更會員資訊、變更付款資訊、變更密碼、忘記密碼)
    • 商品管理系統(商品內容之 CRUD、庫存管理等)
    • 金流、物流系統

至於你說 本日精選人氣精選 類的確應該會是後端提供,而過度使用的問題需要綜合各個面向才能較為準確的評斷。

  1. 資料夾結構問題則見仁見智,我個人是看該 Service 被共用的程度來決定,如果沒有其他模組或元件需要使用的話就不會放到其他的資料夾裡。
0
obelisk0114
iT邦新手 5 級 ‧ 2019-12-03 12:49:53

大大你好,我在 product-list 的切換有點問題

我點選左邊的 本日精選人氣推薦等等,路由可以成功跳轉,但是畫面卻不會更新。同一個元件,不同路由的情況,是不是不會重新載入 ? 要如何在路由跳轉之後更新 product-list ?

product-list.html 相關的部分

<ng-container *ngFor="let product of filteredProduct">
    <app-product-item [item]="product"></app-product-item>
</ng-container>

<div class="pagination" *ngIf="filteredProduct.length > productPerPage">
    <ul class="font-dark-green">
        <li>
            <i class="material-icons" 
               (click)="changePage($event.target)">arrow_left</i>
        </li>
        <li *ngFor="let i of pagination" 
            (click)="changePage($event.target)">{{ i }}</li>
        <li>
            <i class="material-icons" 
               (click)="changePage($event.target)">arrow_right</i>
        </li>
    </ul>
</div>

product-list.ts 相關的部分

productLists: IProduct[] = [];      // 這個分類的全部商品
filteredProduct: IProduct[] = [];   // 只有這一頁的商品
isEmpty = false;                    // 商品列表是否為空
productPerPage = 6;                 // 每頁最多有幾個商品
pagination: number[] = [];          // 這裡放頁碼

ngOnInit() {
    /// 下面這 2 個都不行
    //let type = this.route.snapshot.paramMap.get('type');
    let type: string;
    this.route.params.subscribe( routeParams => type = routeParams.type );
    console.log(type);

    if (type === this.productType.all) {   // 全部商品
      this.retrieveProductsService.getProducts().subscribe({
        next: productLists => {
          this.productLists = productLists;
          /// TODO: filteredProduct 只設定這一頁
          this.filteredProduct = this.productLists;

          if (this.productLists.length === 0) {
            this.isEmpty = true;
          }
          else {
            this.isEmpty = false;

            for (let i = 0; i < Math.ceil
                  (this.productLists.length / this.productPerPage); i++) {
              this.pagination.push(i + 1);
            }
          }
        }
      });
    }
    else {     // 分類商品
      this.retrieveProductsService.getProductsByType(type).subscribe({
        next: productLists => {
          /// TODO: 這邊和上面一樣,之後再改成 call function 來 refactor
        }
      });
    }

  }
看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2019-12-03 14:21:21 檢舉

Hi obelisk0114,

我點選左邊的 本日精選、人氣推薦等等,路由可以成功跳轉,但是畫面卻不會更新。同一個元件,不同路由的情況,是不是不會重新載入 ? 要如何在路由跳轉之後更新 product-list ?

不同路由同個元件是會重新載入的,因為那其實是用同個 class 所產出實體,並不是真的指同一個實體。

從上述程式碼中無法看出你的問題出在哪裡,可能要更完整或做個範例才會知道你的問題在哪裡。

大大你好,我放上 GitHub 了
除了將甜點放到 json 並使用 rxjs 來獲取,使用 http interceptor 進行例外處理,其他都是按照大大的架構
謝謝大大
https://github.com/obelisk0114/Angular30DaysECApplication

Leo iT邦新手 3 級 ‧ 2019-12-05 10:33:43 檢舉

Hi obelisk0114,

問題出在你的 ngOnInit 裡。

首先, snapshot 的中文意思是快照,意即它只會拿到剛進該路由時的那瞬間的資料,而後續如果路由的參數有所變化,它是不會有任何變動的,因此你用 subscribe 的方式是對的。

但你要記得,非同步同步的差異之處,你原本的寫法雖然在初次因速度很快的關係,看似達到同步的效果,但實則其為非同步的,因此雖然你有收到變化過後的路由資料,可卻沒有做任何處理。

以下是我根據你的程式碼稍微微調後的結果,你可以試試看:

ngOnInit() {
  this.route.params.subscribe((routeParams) => {
    const type = routeParams.type;
    // 以 "六角學院" 在 product 頁面呈現的 6 張圖做為預設 product,網址為 /products 時使用
    if (type === this.productType.default) {
      this.productLists = this.defaultProducts;
      this.filteredProduct = this.productLists;
    }
    else if (type === this.productType.all) {
      this.retrieveProductsService.getProducts().subscribe((productLists) => {
        this.setProductListPage(productLists);
      });
    }
    else {
      this.retrieveProductsService.getProductsByType(type).subscribe((productLists) => {
        this.setProductListPage(productLists);
      });
    }
  });
  this.route.queryParamMap.subscribe((paramMap) => {
    const queryPage = paramMap.get('page');
    if (queryPage) {
      this.currentPage = +queryPage;
    }
  });
}

感謝大大幫忙 debug,
根據大大的程式碼,我調整好了 product-list 部分,頁面也可以跳轉了。

  1. 以這個例子來看,product-list 在 人氣推薦本日精選 (相同元件不同路由)是不是只有在第一次進入,才會完整執行 ngOnInit() (subscribe 內 + subscribe 外的部分),之後跳轉只會執行 subscribe 內的部分,而且還是同一個實體,全部的 property 都保留 ?

因為我發現之前的頁碼還有保留,我原先預期會全部重新建立,因此我並沒有清空頁碼陣列,直接把新頁碼 push 進去。結果卻是,若從 本日精選 切到 所有甜點,底下頁碼會變成 1, 2, 1。因此我猜測第一次進入才會建立元件,之後跳轉只會 publish observable,因此 ngOnInit 也只會執行 subscribe 內部

  1. 只在 subscribe 內出現的變數,像是這裡的 typequeryPage 是不是在下一個 observable 來之前就結束生命週期了 ? 不然 const 應該是不能修改值的
Leo iT邦新手 3 級 ‧ 2019-12-09 09:28:35 檢舉
  1. 以這個例子來說,其實是同一個路由,只是參數的值不同而已。
  2. 這個 component 只要還活著,他們的生命週期就還沒結束。

所以其實是同一個路由,只有改變路由和 query 參數,其他都一樣。因為在 ngOnInit 時,我們設置 subscribe,所以路由和 query 參數變化時會收到通知,執行 subscribe 內部而改變 productListsfilteredProductcurrentPage ?

Leo iT邦新手 3 級 ‧ 2019-12-12 10:52:05 檢舉

是的!

product 部分完成了,感謝大大及時的說明,讓小弟從 Angular 菜逼進化成 Angular 新手

Leo iT邦新手 3 級 ‧ 2019-12-13 09:38:11 檢舉

很高興能夠幫到你^^

0
ryan851109
iT邦新手 5 級 ‧ 2022-02-14 14:04:48

您好~我稍微修改了home頁底下的每日精選的列表,讓它的內容從本身的home.component.ts去取值出來,不過發現在home頁F5時,它會多次去呼叫這個函式,但如果是從其他頁面切過來卻是呼叫2次,不知道這個呼叫的次數不一樣是為甚麼呢?不是應該都只有一次才對?
想請您幫忙解答一下謝謝~
示意圖:
(home頁每日精選&console)
https://ithelp.ithome.com.tw/upload/images/20220214/20108518VO5R7ouzOM.png
(home.component.ts取得資料的函式程式碼)
https://ithelp.ithome.com.tw/upload/images/20220214/20108518HYIHuqlTh5.png
(home.component.html每日精選的程式碼)
https://ithelp.ithome.com.tw/upload/images/20220214/20108518mEVNe7AiXd.png
(被home.component.ts呼叫的productService程式碼)
https://ithelp.ithome.com.tw/upload/images/20220214/20108518YT82jr3Ct1.png

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2022-02-14 14:25:58 檢舉

Hi ryan851109,

這是因為你把 getProducts() 直接讓 Template 幫你執行,所以每次畫面渲染的時候都會直接呼叫該函式,這會造成效能問題喔!

原來如此~看來我寫出一個很恐怖的頁面XD
後來我新增一個變數products去裝呼叫service回傳的內容,並把呼叫service的部分移到ngOnInit裡就成功了,可以繼續完成剩下的功能了~感謝版主的幫忙
示意圖:
(home.component.ts取得資料的函式程式碼)
https://ithelp.ithome.com.tw/upload/images/20220214/20108518KpExPMaLYz.png
(home.component.html每日精選的程式碼)
https://ithelp.ithome.com.tw/upload/images/20220214/20108518nzljyPZddQ.png

Leo iT邦新手 3 級 ‧ 2022-02-18 09:57:02 檢舉

/images/emoticon/emoticon12.gif

在版主另一篇發現相關問題解法~貼過來分享一下
https://medium.com/javarevisited/why-you-should-not-use-functions-in-angular-html-f445371a4b6b

0
ryan851109
iT邦新手 5 級 ‧ 2022-02-16 14:07:04

不好意思~這次換成在product頁面出現了一點小問題,我在product頁面的左側分類列表加上了顏色來標註,提醒使用者當前點選哪個分類
示意圖:
https://ithelp.ithome.com.tw/upload/images/20220216/20108518wsMs41FIdJ.png
然而出現了兩個小問題
1.當點分類選擇本日精選後再按F5,標註會跳回預設值,不過URL的路徑卻沒改變
示意圖:
https://ithelp.ithome.com.tw/upload/images/20220216/20108518mROVSOuDDa.png
2.當分類選擇本日精選後直接點擊右上方的甜點,URL路徑會導回product預設的分類,但左側的分類卻無改變
示意圖:
https://ithelp.ithome.com.tw/upload/images/20220216/20108518GjFwFtEGDH.png
相關程式碼示意圖:
(product.html)
https://ithelp.ithome.com.tw/upload/images/20220216/20108518Wjj8gY2E13.png
(product.component.ts)
https://ithelp.ithome.com.tw/upload/images/20220216/20108518Vpx3qFKw1Z.png
(product.router.ts)
https://ithelp.ithome.com.tw/upload/images/20220216/20108518JZkmQDYqMw.png

自己嘗試:
問題一:
目前嘗試過在product.component.ts的 ngOnInit加入一些程式碼
程式碼示意圖:
https://ithelp.ithome.com.tw/upload/images/20220216/20108518ikUbIXKbSF.png
透過對ActivatedRoute中的paramMap做subscribe,好像對問題一沒有幫助,印出來的type是空值,不過如果是對Router的url去parse卻可以抓取到type,這樣就能解決問題一,不過不確定這樣的方式是否正確?

問題二:
目前的想法是在點擊甜點的分頁時,用問題一的方式去parse URL,不過依舊沒有效果QQ

2022/02/16 15:54 更:
問題二的部分參考 : https://blog.csdn.net/wjyyhhxit/article/details/106673196
的做法去監聽Router.events中NavigationEnd的URL變化,發現URL有分為url跟urlAfterRedirects,urlAfterRedirects才有包含甜點的分類,url為一開始傳進路由沒有包含類別的樣子,因此把product.component.ts的程式碼修改如下
程式碼示意圖:
https://ithelp.ithome.com.tw/upload/images/20220216/20108518SoTaFqQpZo.png
並且發現因為product.component.ts中的ngOnInit有在監聽URL的變化,會自動更改,因此在product.html中,點選分類時就不用再去呼叫函示去做修改當前選定type的動作
程式碼示意圖:
https://ithelp.ithome.com.tw/upload/images/20220216/20108518X8xdYPSZbf.png
雖然問題二解決了,不過還是想請教一下筆者會如何解決這樣的問題或者我這樣的解法是否正確?

2022/2/17 18:19 更:
發現如果使用監聽Router.events的方式,每次在切換頁面再回到甜點這個頁面時其Router.events中的observers陣列會一直新增新的值進去,不知是否會造成效能上的問題?這樣是否需要在離開此頁面時所觸發的ngOnDestroy裡面加上對Router.events的unsubscribe,或者還是有其他的方式可以做處理呢?

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2022-02-18 10:03:13 檢舉

Hi ryan851109,

看得出來你很用心也有努力再學習,很棒!!

其實以這個頁面來說,側邊欄的甜點類別是一種 filter, 根據使用者的選擇,把他想要找的甜點類型 filter 出來然後顯示到畫面上而已。

我覺得你可以根據這個思路去做會比較好,試試看吧!

感謝版主的提點,根據提點把紀錄甜點類別的變數拉到 product.component.ts / product-list.component.ts共用的service
利用 service在整個系統只有單一實體的特性,共用這個甜點類別變數,這樣就不用再額外去監聽當前的 Router.events,程式碼也好看許多
程式碼示意圖:
(product.component.ts)
https://ithelp.ithome.com.tw/upload/images/20220218/20108518GBnRqIPLmn.png
(product.html)
https://ithelp.ithome.com.tw/upload/images/20220218/20108518MqkmsF6719.png
(product-list.component.ts)
https://ithelp.ithome.com.tw/upload/images/20220218/20108518Wj2iiUoLqS.png
(product-list.html)
https://ithelp.ithome.com.tw/upload/images/20220218/201085185prypx4ldE.png
(product.service.ts)
https://ithelp.ithome.com.tw/upload/images/20220218/20108518TsmUv210qc.png
大致上只差串聯購物車的資料了^o^/^o^/
不過還是不太曉得為甚麼在 product-list.component.ts中 subscribe ActivatedRoute.paramMap不會有像product.component.ts中 subscribe Route.events一樣一直增加新的observers值?

Leo iT邦新手 3 級 ‧ 2022-02-22 11:55:18 檢舉

Hi ryan851109, 恭喜你!

那個 Observable 應該只會在 paramMap 有變化時才會收到通知才對,不曉得你是不是要問這個問題?

好像是相關的~但不確定(擦汗
這個問題是發生在product.component.ts中,為了防止按下F5後左側類別會跳回原本的初始值,故在ngOnInit中加上subscribe Router.events的NavigationEnd,此處不是subscribe ActivatedRoute.paramMap,因為發現按下F5後先打進路由的路徑並不包含type的類別,需要等待路由將其轉發後才會有,使用ActivatedRoute.paramMap無法抓到轉發後的type
不過卻發現使用subscribe Router.events的NavigationEnd這樣的方式造成 Router.events中的observers陣列 一直增加(如圖)
示意圖:
https://ithelp.ithome.com.tw/upload/images/20220222/20108518OLe9doauyf.png
每次切換頁面回到甜點頁其陣列都加一,懷疑是否元件在切換頁面時只會銷毀元件本身的資料,不會清除路由的相關資料,因此我subscribe住後沒有釋放,他就會一直堆疊在Router.events中的observers,越疊越多,雖然product-list.component.ts中有subscribe ActivatedRoute.paramMap,但因ActivateRoute沒有類似存放events的陣列,所以才不會發生像Router.events的情形,以上是我的想法,不確定對不對?(再次擦汗XD

Leo iT邦新手 3 級 ‧ 2022-02-24 16:01:14 檢舉

Hi ryan851109,

一般我會直接訂閱 activatedRoute.queryParams ,我個人沒用過 paramMap ,你可以試試看~

您好~使用paramMap是參考筆者在obelisk0114問題裡面的寫法,不過當時是用params,差別只在取值時要用[]或GET,筆者建議用activatedRoute.queryParams的方法在product.component.ts中取不到值,不知道是否跟路由有關?
示意圖:
(在product.component.ts的ngOnInit取路徑的queryParams)
https://ithelp.ithome.com.tw/upload/images/20220224/201085180lWlPWIcvu.png
(log印出queryParams中為空值,沒有取得URL的ALL)
https://ithelp.ithome.com.tw/upload/images/20220224/201085188TZeZWhDfA.png

0
Yori
iT邦新手 5 級 ‧ 2022-02-24 17:24:34

目前做到cart跟checkout 訂單摘要那邊,目前在server那邊搞了一個固定值money=100,請問如何在cart的結帳點擊事件中,直接新的數值取代原本server的money?

看更多先前的回應...收起先前的回應...
Leo iT邦新手 3 級 ‧ 2022-02-24 17:39:26 檢舉

Hi zx963302,

有點不太懂你想要做什麼,可能要再麻煩你講清楚一點@@

Yori iT邦新手 5 級 ‧ 2022-02-24 17:42:36 檢舉

訂單摘要那邊的總計,我有計算一個新的值,我現在按下結帳時,把server裡的money也改成新的值

Leo iT邦新手 3 級 ‧ 2022-02-24 17:46:44 檢舉

嗯?感覺你已經說出答案了

「按下結帳時,將 money 更改為新的值」不是嗎?

如果你想知道確切怎麼做的話,可能會需要你提供程式碼,不然我不會知道你的程式碼是怎麼寫的^^"

Yori iT邦新手 5 級 ‧ 2022-02-24 18:03:45 檢舉

抱歉 不用了,發現是自己耍智障
upData(total:any){this.price = total}; 忘記打total:any

Leo iT邦新手 3 級 ‧ 2022-02-25 15:02:01 檢舉

型別最好不要用 any ,該是什麼型別就是什麼型別,不然幹嘛用 TypeScript XD

我要留言

立即登入留言